package exercicios_threads.threads_aracaju.produtor_consumidor;

/**
 * Classe representando o buffer limitado a ser compartilhado pelas threads 
 * Produtoras e Consumidoras
 */
class BufferLimitado {
	/* tamanho do buffer */
	private final static int TAMANHO = 5;	
	/* buffer */ 
	private int[] buffer = new int[TAMANHO];
	/* posicao para insercao de novo item (in) */
	private int posicaoInsere = 0;
	/* posicao para remocao do item (out) */
	private int posicaoRetira = 0;
	/* quantidades de itens no buffer */
	private int quantItens = 0;
	
	/* Construtor */
	public BufferLimitado () {
	}

	/**
	 * Insere um novo item no buffer
	 * @param novoItem item a ser inserido no buffer
	 */
	synchronized public void insere (int novoItem) {
		/* enquanto o buffer estiver cheio a thread deve esperar */
		while (quantItens == TAMANHO) {
			try {
				wait();
			} catch (InterruptedException e){
				System.out.println("Erro de Estado da Thread " 
				                   + e.getMessage());
			}
		}
		/* colocando novo item no buffer */
		buffer[posicaoInsere] = novoItem;
		/* exibindo informacoes sobre a insercao do novo item */
		System.out.print("Inserindo " + novoItem + "\tna posicao " + posicaoInsere + "\t");
		/* atualizando a posicao de insercao */
		posicaoInsere = proximaPosicao(posicaoInsere);
		/* incrementando a quantidade de itens no buffer */
		quantItens++;
		/* imprimindo o buffer */
		imprime();
		/* caso o buffer estivesse vazio, acordar as threads consumidoras */
		if (quantItens == 1) {
			notifyAll();
		}
	}
	
	/** 
	 * Retira um item do buffer 
	 * @return Item retirado do buffer
	 */
	synchronized public int retira () {
		int itemRetirado;	// item retirado
		/* enquanto o buffer estiver vazio a thread deve esperar */
		while (quantItens == 0) {
			try {
				wait();
			} catch (InterruptedException e){
				System.out.println("Erro de Estado da Thread " 
									+ e.getMessage());
			}
		}
		/* armazendo o item a ser retirado */
		itemRetirado = buffer[posicaoRetira];
		/* atualizando a quantidade de itens */
		quantItens--;
		/* exibindo informacoes sobre a retirado do item do buffer */
		System.out.print("Retirando " + itemRetirado 
						  + "\tda posicao " + posicaoRetira + "\t");
		/* atualizando a  posicao de retirado */
		posicaoRetira = proximaPosicao(posicaoRetira);
		/* imprimindo o buffer */
		imprime();
		/* caso o buffer estivesse cheio, acordar as threads produtoras */
		if (quantItens == TAMANHO - 1) {
			notifyAll();
		}
		
		/* retorna o item retirado */
		return itemRetirado;
	}
	
	/**
	 * Obtem a proxima posicao no buffer 
	 * @param posicaoAtual a atual posicao no buffer
	 * @return a promixa posicao no buffer
	 */
	private int proximaPosicao (int posicaoAtual) {
		/* obtem a nova posicao */
		int novaPosicao = ++posicaoAtual;
		/* caso ultrapasse o tamanho do buffer,ira para a primeira posicao (0) */
		if (novaPosicao > TAMANHO - 1) {
			novaPosicao = 0;
		}
		
		return novaPosicao;
	}
	
	/**
	 * Obtem a posicao anterior no buffer 
	 * @param posicaoAtual a atual posicao no buffer
	 * @return a posicao anterior no buffer
	 */
	private int posicaoAnterior (int posicaoAtual) {
		/* obtem a nova posicao */
		int novaPosicao = --posicaoAtual;
		/* caso seja menor que a posicao inicial, ira para a ultima posicao */
		if (novaPosicao < 0) {
			novaPosicao  = TAMANHO-1;
		}
		
		return novaPosicao;
	}
	
	/*
	 * Imprime o buffer
	 */
	@SuppressWarnings("unused")
	synchronized private void imprime () {
		/* posicao inicial da impressão */
		int inicio = posicaoRetira;
		/* posicao final da impressão */
		int fim = posicaoAnterior(posicaoInsere);
		/* posicao impressa */
		int i = inicio;
		/* quantidade de itens impressos */
		int quantItensImpressos = 0;
		System.out.print("\tBuffer[" + quantItens + "] \t= ");
		while (quantItensImpressos < quantItens) {
			System.out.print(buffer[i] + " ");
			i = proximaPosicao(i);
			quantItensImpressos++;
		}
		System.out.println();
	}
	
}
